﻿//*** Guidance generated code ***//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;

using ProjectBase.Core;
using ProjectBase.Core.Service;

using $prjsn$.Common;

namespace $safeprojectname$
{
    public class EntityArrayValueProvider<TEntity, TID> : IValueProvider, IService, IPublishingReporter, IMessagesHolder
        where TEntity : class, IPersistentObjectWithTypedId<TID>
    {
        #region Read
        public virtual object GetValue(object target)
        {
            var result = new JArray();

            var list = PropertyInfo.GetValue(target, null) as IList<TEntity>;
            if (list.IsEmpty())
            {
                return result;
            }

            // the MetaData Attribute for current property
            var attribute = Attributes
                .FirstOrDefault(a => a.Name.IsEqual(PropertyInfo.Name));

            foreach (var entity in list)
            {
                if (AllInnerProperties.IsEmpty())
                {
                    var serializer = CreateJsonSerializer(attribute.Attributes);
                    var jObject = JObject.FromObject(entity, serializer);

                    result.Add(jObject);
                    continue;
                }

                var arrayName = PropertyInfo.Name;

                var innerProperties = AllInnerProperties
                    .Where(p => p.StartsWith(arrayName + '.'))
                    .Select(p => p.Remove(0, arrayName.Length).TrimStart('.'))
                    .OrderBy(p => p)
                    .ToList();

                var provider = new PersistentValueProvider(entity, arrayName, innerProperties);
                var obj = provider.GetValue(target);

                result.Add(obj);
            }

            return result;
        }
        #endregion Read

        #region Write
        public virtual void SetValue(object target, object value)
        {
            var jArray = value as JArray;

            var shouldBind = jArray.Is()
                             && PropertyInfo.PropertyType.IsGenericType
                             && PropertyInfo.PropertyType.HasType<IEnumerable>();

            if (!shouldBind)
            {
                return;
            }

            var list = PropertyInfo.GetValue(target, null) as IList<TEntity>;

            var array = new List<JObject>(jArray.Cast<JObject>().ToList());

            var attribute = Attributes
                .FirstOrDefault(a => a.Name.IsEqual(PropertyInfo.Name));

            var canEdit = attribute.Is()
                          && attribute.CanEdit;

            if (!canEdit)
            {
                return;
            }

            BindExisting(array, list, attribute);
            BindNew(array, list, attribute);
        }

        protected virtual void BindNew(IList<JObject> array, IList<TEntity> list, MDAttribute attribute)
        {
            var readFacade = Factory.CreateInstance<IReadFacade<TEntity, TID>>();

            var justAdded = array
                .Where(t => t["ID"].IsNull())
                .ToList();

            foreach (var jItem in justAdded)
            {
                var persistent = readFacade.CreateNew();

                var reader = jItem.CreateReader();
                var serializer = CreateJsonSerializer(attribute.Attributes, NullValueHandling.Include, persistent);

                persistent = serializer.Deserialize<TEntity>(reader);
                //serializer.Populate(reader, persistent);

                list.Add(persistent);
            }
        }

        protected virtual void BindExisting(IList<JObject> array, IList<TEntity> list, MDAttribute attribute)
        {
            var existing = array
                .Where(FindExisting)
                .ToList();
            
            var toRemove = new List<TEntity>();

            foreach (var persistent in list)
            {
                var jItem = existing
                    .FirstOrDefault(t => t["ID"].Value<TID>().Equals(persistent.ID))
                    ;

                if (jItem.Is())
                {
                    var reader = jItem.CreateReader();

                    var serializer = CreateJsonSerializer(attribute.Attributes, NullValueHandling.Include, persistent);

                    serializer.Deserialize<TEntity>(reader);
                }
                else
                {
                    toRemove.Add(persistent);
                }
            }
            foreach (var removeItem in toRemove)
            {
                list.Remove(removeItem);
            }
        }

        protected virtual bool FindExisting(JObject token)
        {
            if (token["ID"].IsNull())
            {
                return false;
            }
            try
            {
                var value = token["ID"].Value<TID>();
                if (value.Is())
                {
                    return true;
                }
            }
            catch (Exception) { }
            return false;
        }

        #endregion Write

        protected virtual JsonSerializer CreateJsonSerializer(IList<MDAttribute> attributes
            , NullValueHandling? nullValueHandling = null, TEntity existing = null)
        {
            Resolver.Attributes = attributes;
            var jSettings = new JsonSerializerSettings
            {
                ContractResolver = Resolver,
            };

            if (existing.Is())
            {
                Converter.Attributes = attributes;
                Converter.ExistingEntity = existing;
                jSettings.Converters.Add(Converter);
            }

            jSettings.NullValueHandling = nullValueHandling.Is()
                ? nullValueHandling.Value
                : (NullValueHandling)Project.Settings.NullValueHandling;

            var serializer = JsonSerializer.Create(jSettings);

            return serializer;
        }

        public virtual PropertyInfo PropertyInfo { protected get; set; }
        public virtual EntityResolver<TEntity, TID> Resolver { protected get; set; }
        public virtual EntityConverter<TEntity, TID> Converter { protected get; set; }

        public virtual IList<string> AllInnerProperties { protected get; set; }
        public virtual IList<MDAttribute> Attributes { protected get; set; }
        public Messages Messages { get; set; }
    }
}